package cn.zbacm.api.security.service;

import cn.zbacm.api.security.CustomUser;
import cn.zbacm.api.security.dto.JwtSubject;
import cn.zbacm.api.user.entity.User;
import cn.zbacm.api.user.entity.UserRole;
import cn.zbacm.api.user.repository.UserRoleRepository;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
  @Autowired
  private JwtTokenProvider tokenProvider;

  @Autowired
  private CustomUserDetailsService customUserDetailsService;

  @Autowired
  private UserRoleRepository userRoleRepository;

  @Override
  protected void doFilterInternal(
          HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
          throws ServletException, IOException {
    MDC.put("user_id", "");
    MDC.put("user_username", "");
    MDC.put("user_phone", "");
    log.info("JwtAuthenticationFilter start to filter for {}", request.getRequestURI());
    String jwt = getJwtFromRequest(request);
    JwtSubject jwtSubject = tokenProvider.validateToken(jwt);
    if (jwtSubject == null) {
      log.info("request doesn't have jwt token");
      filterChain.doFilter(request, response);
      return;
    }

    try {
      UserDetails userDetails = customUserDetailsService.loadUserById(jwtSubject.getUserId());
      log.info("userDetails loaded");
      User user = ((CustomUser) userDetails).getUser();
      List<GrantedAuthority> authorities = new ArrayList<>();
      if (jwtSubject.isHasRole()) {
        List<UserRole> userRoles = userRoleRepository.findByUserId(user.getId());
        log.info("{} userRoles loaded", userRoles.size());
        authorities =
                userRoles.stream()
                        .map(userRole -> new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId())))
                        .collect(Collectors.toList());
      }
      UsernamePasswordAuthenticationToken authentication =
              new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
      log.info("authentication token loaded");
      //      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      SecurityContextHolder.getContext().setAuthentication(authentication);
      log.info("set authentication succeed");
      MDC.put("user_id", String.valueOf(user.getId()));
      MDC.put("user_username", String.valueOf(user.getUsername()));
      MDC.put("user_phone", String.valueOf(user.getPhone()));
    } catch (UsernameNotFoundException ignore) {
      log.info("UsernameNotFoundException occurred");
    } catch (Exception ex) {
      log.error("couldn't set user authentication: {}", ex.getMessage());
    }

    filterChain.doFilter(request, response);
  }

  private String getJwtFromRequest(HttpServletRequest request) {
    String bearerToken = request.getHeader("Authorization");
    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
      return bearerToken.substring(7);
    }
    return null;
  }
}
